package icu.funkye.confine;

import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;

import icu.funkye.confine.aop.ConfineMethodAspect;
import icu.funkye.confine.common.concurrent.ConfineThreadFactory;
import icu.funkye.confine.failHandle.ConfineFailHandle;
import icu.funkye.confine.failHandle.DefaultConfineFailHandle;
import icu.funkye.confine.limiter.RateLimiterFactory;
import icu.funkye.confine.limiter.product.Limiter;
import icu.funkye.confine.properties.ConfineProperties;
import icu.funkye.confine.spring.interceptor.ConfineInterceptor;
import icu.funkye.confine.spring.scanner.ConfineScanner;
import icu.funkye.confine.wrapper.ResourceWrapper;
import icu.funkye.confine.constants.ConfineConstant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Import;

/**
 * @author jianbin.chen
 */
@ConditionalOnWebApplication
@EnableConfigurationProperties(ConfineProperties.class)
@ConditionalOnProperty(prefix = ConfineProperties.CONFINE_PREFIX, name = "enabled", havingValue = "true",
    matchIfMissing = true)
@Import(ConfineMethodAspect.class)
public class ConfineAutoConfiguration {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfineAutoConfiguration.class);

    private static volatile ScheduledThreadPoolExecutor EXECUTOR;

    @Autowired
    ConfineProperties properties;

    @PostConstruct
    public void init() {
        LOGGER.info("CONFINE init.....");
        RateLimiterFactory.init(RateLimiterFactory.LimiterType.get(properties.getType()));
    }

    @Bean
    @ConditionalOnMissingBean(ConfineFailHandle.class)
    public ConfineFailHandle confineFailHandle() {
        return new DefaultConfineFailHandle();
    }

    @Bean
    @DependsOn("confineFailHandle")
    @ConditionalOnMissingBean(ConfineInterceptor.class)
    public ConfineInterceptor confineInterceptor(ConfineFailHandle confineFailHandle) {
        // 每分钟通过load变更当前最大tps,计算公式 默认最大tps*目前空闲率
        EXECUTOR = new ScheduledThreadPoolExecutor(1, new ConfineThreadFactory("computeTps"));
        ConfineInterceptor confineInterceptor = new ConfineInterceptor(confineFailHandle,
            this.properties.getGlobalMaxTps(), this.properties.getGlobalMaxRtThreshold());
        if (EXECUTOR.getActiveCount() <= 0 && this.properties.getGlobalMaxTps() > 0) {
            EXECUTOR.scheduleAtFixedRate(() -> {
                int maxTps = this.properties.getGlobalMaxTps();
                com.sun.management.OperatingSystemMXBean bean =
                    (com.sun.management.OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
                Double load = bean.getSystemCpuLoad();
                if (load > 0) {
                    ResourceWrapper resourceWrapper = RateLimiterFactory.create(ConfineConstant.DEFAULT_METHOD, maxTps);
                    Limiter rateLimiter = resourceWrapper.getRateLimiter();
                    BigDecimal standard = new BigDecimal(1);
                    BigDecimal loadRate = new BigDecimal(/** 转string避免丢失精度 **/
                        String.valueOf(load));
                    BigDecimal tps = new BigDecimal(maxTps);
                    int currentTps = standard.subtract(loadRate).multiply(tps).intValue();
                    rateLimiter.setTps(currentTps);
                    confineInterceptor.setGlobalMaxTps(currentTps);
                }
            }, 0, 60, TimeUnit.SECONDS);
        }
        return confineInterceptor;
    }

    @Bean
    @ConditionalOnMissingBean(ConfineScanner.class)
    public ConfineScanner confineScanner() {
        return new ConfineScanner();
    }

}
